#include <windows.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>

/*
命令行编译参数
g++ -O3 tetris.cpp -lgdi32 -luser32  -o tetris
g++ -O3 tetris.cpp -mwindows -o tetris -static
windres xx.rc -o xx.o
*/

#define W 10
#define H 20
#define S 25
#define RW 7
#define TIMER_ID 1234

struct SHAPE
{
    POINT pt[4][4];
    int count;
    int index;
};

struct BRUSH
{
    HBRUSH handle;
    BRUSH(COLORREF c){handle = CreateSolidBrush(c);}
    ~BRUSH(){DeleteObject(handle);}
};

struct GDI
{
    HPEN null_pen;
    HPEN pen3;
    HDC memery_dc_left;
    HBITMAP bitmap_left;
    HDC memery_dc_right;
    HBITMAP bitmap_right;
    HBRUSH backgroundbrush;
    HBRUSH light_brush;
    HBRUSH dark_brush;
    HFONT font;

    GDI(HDC hdc)
    {
        /* 画笔 */
        null_pen = (HPEN)GetStockObject(NULL_PEN);
        pen3 = CreatePen(PS_SOLID, 3, RGB(0, 0, 0));


        /* 双缓存 */
        memery_dc_left = CreateCompatibleDC(hdc);        
        bitmap_left = CreateCompatibleBitmap(hdc, W * S, H * S); 
        SelectObject(memery_dc_left, bitmap_left);

        memery_dc_right = CreateCompatibleDC(hdc);
        bitmap_right = CreateCompatibleBitmap(hdc, RW * S, H * S);
        SelectObject(memery_dc_right, bitmap_right);

        /* 背景画刷 */
        backgroundbrush = CreateSolidBrush(RGB(0x74, 0x8b, 0x7b));

        /* 浅色方块画刷 */
        RECT rt = {0, 0, S, S};
        HDC temp_dc = CreateCompatibleDC(hdc);
        HBITMAP temp_bitmap = CreateCompatibleBitmap(hdc, S, S);
        SelectObject(temp_dc, temp_bitmap);

        HPEN light_pen = CreatePen(PS_SOLID, 3, RGB(0x6f, 0x86, 0x76));        
        SelectObject(temp_dc, light_pen);
        SelectObject(temp_dc, backgroundbrush);
        FillRect(temp_dc, &rt, backgroundbrush);
        Rectangle(temp_dc, 2, 2, S - 2, S - 2);
        HBRUSH light = CreateSolidBrush(RGB(0x6f, 0x86, 0x76));
        SelectObject(temp_dc, light);
        Rectangle(temp_dc, 8, 8, S - 8, S - 8);
        light_brush = CreatePatternBrush(temp_bitmap);

        /* 深色方块画刷 */
        
        SelectObject(temp_dc, pen3);
        SelectObject(temp_dc, backgroundbrush);
        FillRect(temp_dc, &rt, backgroundbrush);
        Rectangle(temp_dc, 2, 2, S - 2, S - 2);
        SelectObject(temp_dc, (HBRUSH)GetStockObject(BLACK_BRUSH));
        Rectangle(temp_dc, 8, 8, S - 8, S - 8);
        dark_brush = CreatePatternBrush(temp_bitmap);

        DeleteObject(temp_bitmap);
        DeleteObject(light_pen);
        DeleteObject(light);
        DeleteDC(temp_dc);

        /* 字体 */
        LOGFONT lf = {0};
        lf.lfHeight = 36;
        strcpy(lf.lfFaceName, "Impact");
        font = CreateFontIndirect(&lf);
    }

    ~GDI()
    {
        DeleteObject(bitmap_left);
        DeleteDC(memery_dc_left);
        DeleteObject(bitmap_right);
        DeleteDC(memery_dc_right);
        DeleteObject(backgroundbrush);
        DeleteObject(font);
        DeleteObject(light_brush);
        DeleteObject(dark_brush);
        DeleteObject(pen3);
    }
};

SHAPE g_all_shapes[] = 
{    
    {{{{1,0},{1,1},{1,2},{2,2}}, {{0,1},{0,2},{1,1},{2,1}}, {{0,0},{1,0},{1,1},{1,2}},{{0,1},{1,1},{2,1},{2,0}}}, 4, 0}, /* L */
    {{{{1,0},{1,1},{1,2},{0,2}}, {{0,0},{0,1},{1,1},{2,1}}, {{2,0},{1,0},{1,1},{1,2}},{{0,1},{1,1},{2,1},{2,2}}}, 4, 0}, /* J */
    {{{{1,0},{0,1},{1,1},{2,1}}, {{1,0},{1,1},{1,2},{2,1}}, {{0,1},{1,1},{2,1},{1,2}},{{1,0},{1,1},{1,2},{0,1}}}, 4, 0}, /* T */
    {{{{0,0},{0,1},{1,1},{1,2}}, {{0,1},{1,0},{1,1},{2,0}}}, 2, 0}, /* S */
    {{{{1,0},{0,1},{1,1},{0,2}}, {{0,0},{1,0},{1,1},{2,1}}}, 2, 0}, /* Z */
    {{{{0,1},{1,1},{2,1},{3,1}}, {{1,0},{1,1},{1,2},{1,3}}}, 2, 0}, /* -- */
    {{{{1,1},{1,2},{2,1},{2,2}}}, 1, 0}, /* O */
};

struct TetrisGame
{
    SHAPE current;
    SHAPE next;
    POINT position;
    int data[H][W];
    int score;
    int interval;
    int fps;
    HWND hwnd;
    GDI *p_gdi;
    bool is_pause;

    void init_game()
    {
        srand((unsigned)time(NULL));
        get_random_shape(current);
        get_random_shape(next);
        reset_data();

        HDC hdc = GetDC(hwnd);
        p_gdi = new GDI(hdc);
        ReleaseDC(hwnd, hdc);

        SetTimer(hwnd, TIMER_ID, interval, NULL);
        is_pause = false;
    }

    void trigger_pause()
    {
        if (is_pause)
            SetTimer(hwnd, TIMER_ID, interval, NULL);
        else
            KillTimer(hwnd, TIMER_ID);
        is_pause = !is_pause;
    }

    void set_hwnd(HWND hWnd)
    {
        hwnd = hWnd;
    }

    void quit_game()
    {
        KillTimer(hwnd, TIMER_ID);
        delete p_gdi;
    }

    void get_random_shape(SHAPE &shape)
    {   
        int size = sizeof(g_all_shapes) / sizeof(g_all_shapes[0]);
        int index = rand() % size;
        shape = g_all_shapes[index];
        shape.index = rand() % shape.count;
    }

    void get_next_shape()
    {
        current = next;
        get_random_shape(next);
        reset_position();
    }

    void reset_position()
    {        
        position.x = 4;
        position.y = -2;
    }

    void reset_data()
    {
        reset_position();
        memset(data, 0, H * W * sizeof(data[0][0]));
        score = 0;
        interval = 400; // ms
        fps = 20;
    }

    bool is_point_valid(POINT &pt)
    {
        if (pt.x < 0 || pt.x >= W)
            return false;
        if (pt.y >= H)
            return false;
        if (pt.y < 0) // 越界
            return true;
        if (data[pt.y][pt.x]) 
            return false;
        return true;
    }

    bool can_move(int dx, int dy)
    {
        for (int i = 0; i < 4; i++)
        {
            POINT temp = {current.pt[current.index][i].x + position.x + dx, 
                current.pt[current.index][i].y + position.y + dy};
            if (!is_point_valid(temp))
                return false;
        }
        return true;
    }

    bool move(int dx, int dy)
    {
        if (!can_move(dx, dy))
            return false;

        position.x += dx;
        position.y += dy;
        return true;
    }

    void move_bottom()
    {
        while (can_move(0, 1))
            position.y += 1;
    }

    void rotate()
    {
        int index = current.index++;
        if (current.index >= current.count)
            current.index = 0;
        if (!can_move(0, 0))
            current.index = index;
    }

    bool is_game_over()
    {
        for (int x = 0; x < W; x++)
            if (data[0][x])
                return true;
        return false;
    }

    bool is_line_full(int y)
    {
        for (int x = 0; x < W; x++)
            if (!data[y][x])
                return false;
        return true;
    }

    void kill_one_line(int target)
    {
        for (int y = target; y > 0; y--)
            for (int x = 0; x < W; x++)
                data[y][x] = data[y - 1][x];
        for (int x = 0; x < W; x++)
            data[0][x] = 0;
    }

    int kill_all()
    {
        int count = 0;
        for (int y = 0; y < H; y++)
            if (is_line_full(y))
                kill_one_line(y), count++;
        return count;
    }

    void merge()
    {
        for (int i = 0; i < 4; i++)
        {
            int x = current.pt[current.index][i].x + position.x;
            int y = current.pt[current.index][i].y + position.y;
            if (y < 0) 
                continue;
            data[y][x] = 1;
        }
    }

    void draw_all(HWND hwnd)
    {
        HDC hdc = GetDC(hwnd);

        /* left */
        RECT rt = {0, 0, W * S, H * S};
        FillRect(p_gdi->memery_dc_left, &rt, p_gdi->light_brush);

        /* 画当前的形状*/
        SelectObject(p_gdi->memery_dc_left, p_gdi->dark_brush);
        SelectObject(p_gdi->memery_dc_left, p_gdi->null_pen);
        for (int i = 0; i < 4; i++)
            Rectangle(p_gdi->memery_dc_left, 
                (current.pt[current.index][i].x + position.x) * S, 
                (current.pt[current.index][i].y + position.y) * S, 
                (current.pt[current.index][i].x + position.x) * S + S, 
                (current.pt[current.index][i].y + position.y) * S + S);
        /* 画已经降落的方块 */
        for (int y = 0; y < H; y++)
            for (int x = 0; x < W; x++)
                if (data[y][x])
                    Rectangle(p_gdi->memery_dc_left, x * S, y * S, x * S + S, y * S + S);
        BitBlt(hdc, S, S, W * S, H * S, p_gdi->memery_dc_left, 0, 0, SRCCOPY);

        /* right */
        RECT rt_ = {0, 0, RW * S, H * S};
        FillRect(p_gdi->memery_dc_right, &rt_, p_gdi->backgroundbrush);

        SelectObject(p_gdi->memery_dc_right, p_gdi->pen3);
        MoveToEx(p_gdi->memery_dc_right, 0, 0, NULL);
        LineTo(p_gdi->memery_dc_right, 0, H * S);
        
        SetTextColor(p_gdi->memery_dc_right, RGB(0, 0, 0));        
        SetBkColor(p_gdi->memery_dc_right, RGB(0x74, 0x8b, 0x7b));

        char buffer[10] = {0};
        SelectObject(p_gdi->memery_dc_right, p_gdi->font);
        TextOut(p_gdi->memery_dc_right, S, S, "NEXT", 4);
        TextOut(p_gdi->memery_dc_right, S, 9 * S, "SCORE", 5);
        TextOut(p_gdi->memery_dc_right, 2 * S, 11 * S, buffer, sprintf(buffer, "%d", score));
        TextOut(p_gdi->memery_dc_right, S, 15 * S, "INTERVAL", 8);
        TextOut(p_gdi->memery_dc_right, 2 * S, 17 * S, buffer, sprintf(buffer, "%d", interval));

        POINT next_position = {2, 4};
        SelectObject(p_gdi->memery_dc_right, p_gdi->dark_brush);
        SelectObject(p_gdi->memery_dc_right, p_gdi->null_pen);
        for (int i = 0; i < 4; i++)
            Rectangle(p_gdi->memery_dc_right, 
                (next.pt[next.index][i].x + next_position.x) * S, 
                (next.pt[next.index][i].y + next_position.y) * S, 
                (next.pt[next.index][i].x + next_position.x) * S + S, 
                (next.pt[next.index][i].y + next_position.y) * S + S);

        BitBlt(hdc, (W + 2) * S, S, RW * S, H * S, p_gdi->memery_dc_right, 0, 0, SRCCOPY);

        ReleaseDC(hwnd, hdc);
    }
};

typedef LRESULT (*FUNCTION)(HWND, UINT, WPARAM, LPARAM);
struct MESSAGE_MAP
{
    UINT msg;
    FUNCTION fun;
};

TetrisGame game;

LRESULT create_message(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    game.set_hwnd(hwnd);
    game.init_game();
    return 0;
}

LRESULT keydown_message(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (wParam == 'P')
        game.trigger_pause();

    if (game.is_pause)
        return 0;

    if (wParam == VK_UP)
        game.rotate();
    else if (wParam == VK_DOWN)
        game.move(0, 1);
    else if (wParam == VK_LEFT)
        game.move(-1, 0);
    else if (wParam == VK_RIGHT)
        game.move(1, 0);
    else if (wParam == VK_SPACE)
        game.move_bottom();
    
    return 0;
}

LRESULT timer_message(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (game.is_game_over())
    {        
        game.quit_game();
        MessageBox(hwnd, "GAME OVER", "tetris", MB_OK | MB_ICONEXCLAMATION);
        DestroyWindow(hwnd);
        return 0;
    }
    
    if (game.move(0, 1))
        return 0;

    game.merge();
    int count = game.kill_all();
    if (count)
    {                    
        int add_score = (1 << count - 1) * 10;
        game.score += add_score;
        if (game.interval != max(50, game.interval - add_score))
        {
            game.interval = max(50, game.interval - add_score);
            KillTimer(hwnd, TIMER_ID);
            SetTimer(hwnd, TIMER_ID, game.interval, NULL);
        }
    }
    game.get_next_shape();
    return 0;
}

LRESULT destroy_message(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PostQuitMessage(0);
    return 0;
}

MESSAGE_MAP g_message_map[] = 
{
    {WM_CREATE, create_message},
    {WM_TIMER, timer_message},
    {WM_KEYDOWN, keydown_message},
    {WM_DESTROY, destroy_message}, 
};

/* 事件回调 */
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    for (int i = 0; i < sizeof(g_message_map) / sizeof(g_message_map[0]); i++)
        if (msg == g_message_map[i].msg)
            return g_message_map[i].fun(hwnd, msg, wParam, lParam);
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
{
    WNDCLASS wc = {0};    
    char classname[] = "tetris";
    BRUSH brush(RGB(0x74, 0x8b, 0x7b));
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = brush.handle;
    wc.lpszClassName = classname;
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WndProc;
    if (!RegisterClass(&wc))
        return -1;

    /* 通过ClinetRect大小计算WindowRect */    
    DWORD style = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX;
    RECT rt = {0, 0, (W + RW) * S, H * S};
    AdjustWindowRect(&rt, style, FALSE);
    int w = rt.right - rt.left + 3 * S;
    int h = rt.bottom - rt.top + 2 * S;

    char windowname[] = "tetris";
    HWND hwnd = CreateWindow(classname, windowname, 
        style, CW_USEDEFAULT, CW_USEDEFAULT, w, h,
        NULL, NULL, hInstance, NULL);
    if (hwnd == NULL)
        return -1;
    
    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    /* 主循环 */
    MSG msg;
    DWORD last_tick = GetTickCount();
    while (TRUE)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {            
            TranslateMessage(&msg);
            DispatchMessage(&msg);

            if (msg.message == WM_QUIT)
                break;   
        }
        else
        {
            DWORD current_tick = GetTickCount();
            DWORD elapse_time = current_tick - last_tick;            
            game.draw_all(hwnd); /* 绘图 */
            if (elapse_time < 1000 / game.fps)
                Sleep(1000 / game.fps - elapse_time);
            last_tick = GetTickCount();
        }             
    }

    return (int)msg.wParam;
}
